package com.astersoft.lib.util.singleton.factory;

import java.lang.reflect.ParameterizedType;
import java.util.HashMap;
import java.util.Map;

public class ASSingletonFactory {

	/***** 
	 * The InstanceFactory is a Singleton to make sure there is only one instanceMap 
	 *****/  
	private static final ASSingletonFactory factory = new ASSingletonFactory();  
	public static ASSingletonFactory getFactory() { return factory; }  
	private ASSingletonFactory() {};  

	/***** 
	 * The Map holds Class -> ThingHolder pairings.  Note that the 'ThingHolder' represents the 'Singleton' 
	 * you want per type. 
	 *****/  
	private final Map<Class<?>, ItemHolder<?>> instanceMap = new HashMap<Class<?>, ItemHolder<?>>();  

	/***** 
	 * Return an instance of a ThingHolder which holds an Object of the type described by 
	 * the passed in Class.  Each type will have exactly one instance of ThingHolder, such 
	 * that two calls to getInstance using the same Class Object will return the same  
	 * instance of ThingHolder. 
	 *  
	 * Such that the assertion below should always be true: 
	 * <code> 
	 *     InstanceFactory factory = InstanceFactory.getFactory(); 
	 *     ThingHolder<Integer> holder1 = factory.getInstance(Integer.class); 
	 *     ThingHolder<Integer> holder2 = factory.getInstance(Integer.class); 
	 *     assert(holder1 == holder2); 
	 * </code> 
	 *  
	 * If different Class Objects are passed into the getInstance method then the method 
	 * will return different ThingHolder objects which holds a different type. 
	 *  
	 * Such that the assertion below should always be true: 
	 * <code> 
	 *     InstanceFactory factory = InstanceFactory.getFactory(); 
	 *     ThingHolder<Integer> holder1 = factory.getInstance(Integer.class); 
	 *     ThingHolder<String> holder2 = factory.getInstance(String.class); 
	 *     assert(holder1 != holder2); 
	 * </code> 
	 *****/  
	@SuppressWarnings("unchecked")  
	public <T> ItemHolder<T> getInstance(Class<T> ofClass) {  
		ItemHolder<T> instance = null;  
		synchronized(ofClass)  
		{  
			if (instanceMap.containsKey(ofClass))  
			{  
				instance = (ItemHolder<T>) instanceMap.get(ofClass);  
			}  
			else  
			{  
				instance = new ItemHolderImpl<T>(ofClass);  
				instanceMap.put(ofClass, instance);  
			}  
		}  

		return instance;  
	}  

	/* *** 
	 * Private implementation of the ThingHolder interface. 
	 * ***/  
	private class ItemHolderImpl<T> implements ItemHolder<T>  
	{  
		private T toHold;  

		@SuppressWarnings("unchecked")
		ItemHolderImpl(Class<T> typeToHold) {
			try {
				toHold = ((Class<T>)((ParameterizedType) typeToHold.getGenericSuperclass()).getActualTypeArguments()[0]).newInstance();
			} catch (InstantiationException e) {
				e.printStackTrace();
			} catch (IllegalAccessException e) {
				e.printStackTrace();
			}
		}  

		public T getItem() { return this.toHold; }  
		public void setItem(T toSet) { this.toHold = toSet; }  
	}  
}
